﻿/* Author:
 *  Andrea Russino
 */ 

using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Xml.Serialization;
using System.Security.Cryptography;
using System.Runtime.InteropServices;
using System.Xml;

namespace TripLOAMobile
{
    public class CacheManager : IDisposable
    {
        private const string defaultCacheDir = "cache";
        private const string profileDir = "profiles";
        private const string tracingTableFile = "tracingTable";
        private const int defMaxSize = 1024;
        private const char pwdTagChar = '^'; //tags profiles without password

        private string m_cachePath; //absolutePath for cache directory
        private Dictionary<int, CachingInfo> m_infoTracer; //table to keep trace of cache attributes for each information
        private long m_maxCacheSize; //maximum cache size allowed, expressed in Byte
        private byte[] encodingKey, encodingIV;

        #region Public Members
        /// <summary>
        /// Instantiates a CacheManager and set the cache directory to "cache", subdirectory of
        /// the application directory.
        /// If the directory does not exist, it will be created.
        /// </summary>
        public CacheManager() : this(null) {}

        /// <summary>
        /// Instantiates a CacheManager and set the indicated subdirectory of the application directory.
        /// If the directory does not exist, it will be created.
        /// </summary>
        /// <param name="cachePath">
        /// The path of cache directory.
        /// null value is allowed. If so, the invocation has the same effect of using the null parametered  constructor.
        /// In any case, if not found, the directory will be created.
        ///</param>
        public CacheManager(string cachePath)
        {
            SetupEncodingInfo();

            CachePath = cachePath;
            m_maxCacheSize = defMaxSize;

            m_infoTracer = new Dictionary<int, CachingInfo>();
            try
            {
                AcquireTracingTable();
            }
            catch (Exception e)
            {
                m_infoTracer = ManageMissingTable();
            }
        }

        /// <summary>
        /// Gets or sets the maximum cache size allowed, expressed in byte.
        /// </summary>
        /// <value>The maximum cache size allowed.</value>
        public long MaxCacheSize { get { return m_maxCacheSize; } set { m_maxCacheSize = value; } }

        #region CredentialSpecificMethods
        /// <summary>
        /// Asynchronous storing of a credential as critical informations (that require an encoded serialization).
        /// The credential will be encrypted using the Rijndael algorithm.
        /// </summary>
        /// <param name="data">The credential to be saved.</param>
        /// <param name="fileName">Name of the file to store in.</param>
        public void MT_SaveCriticalData(InternalEntities.Credentials data, string fileName)
        {
            SaveRequest<InternalEntities.Credentials> req = new SaveRequest<InternalEntities.Credentials>(data, fileName);
            System.Threading.WaitCallback job =
                delegate(object state) { SaveCriticalData((SaveRequest<InternalEntities.Credentials>)state); }; 
            System.Threading.ThreadPool.QueueUserWorkItem(job, req);
        }

        /// <summary>
        /// Synchronous storing of a credential as critical informations (that require an encoded serialization).
        /// The Credential will be encrypted using the Rijndael algorithm.
        /// </summary>
        /// <param name="data">The credential to be saved.</param>
        /// <param name="fileName">Name of the file to store in.</param>
        public void SaveCriticalData(InternalEntities.Credentials data, string fileName)
        {
            StringBuilder path = new StringBuilder(256)
                .Append(m_cachePath)
                .Append(profileDir)
                .Append(Path.DirectorySeparatorChar)
                .Append(fileName)
                .Append(pwdTagChar);

            if (data.Password != null){
                if(File.Exists(path.ToString())) ////caso in cui l'utente decide in un secondo momento di salvare la password
                    File.Delete(path.ToString());
                path.Remove(path.Length - 1, 1);
            }

            using (FileStream fStream = File.Open(path.ToString(), FileMode.OpenOrCreate))
                SerializeCriticalData(data, fStream);
        }
        #endregion 

        #region GenericCriticalMethods
        /// <summary>
        /// Asynchronous storing of data as critical informations (that require an encoded serialization).
        /// The credential will be encrypted using the Rijndael algorithm.
        /// </summary>
        /// <param name="data">The data to be saved.</param>
        /// <param name="fileName">Name of the file to store in.</param>
        public void MT_SaveCriticalData(object data, string fileName)
        {
            SaveRequest<object> req = new SaveRequest<object>(data, fileName);
            System.Threading.WaitCallback job =
                delegate(object state) { SaveCriticalData((SaveRequest<object>)state); };
            System.Threading.ThreadPool.QueueUserWorkItem(job, req);
        }

        /// <summary>
        /// Synchronous storing of data as critical informations (that require an encoded serialization).
        /// The credential will be encrypted using the Rijndael algorithm.
        /// </summary>
        /// <param name="data">The data to be saved.</param>
        /// <param name="fileName">Name of the file to store in.</param>
        public void SaveCriticalData(object data, string fileName)
        {
            StringBuilder path = new StringBuilder(256)
                .Append(m_cachePath)
                .Append(profileDir)
                .Append(Path.DirectorySeparatorChar)
                .Append(fileName);

            using (FileStream fStream = File.Open(path.ToString(), FileMode.OpenOrCreate))
                SerializeCriticalData(data, fStream);
        }
        #endregion

        /// <summary>
        /// Retrieves critical informations (that require an encoded serialization) by the cache,
        /// accessing them by filename.
        /// </summary>
        /// <param name="fileName">The name identifying the desidered information</param>
        /// <param name="type">The type expected</param>
        /// <returns>The retrived information, if present; otherwise null if an error occurrs</returns>
        public object GetCriticalData(string fileName, Type type)
        {
            StringBuilder path = new StringBuilder(256)
                .Append(m_cachePath)
                .Append(profileDir)
                .Append(Path.DirectorySeparatorChar)
                .Append(fileName);

            //This allows to check and eventually read the file, even if it is labeled as passwordless
            //For files unrelated to profiles, this doesn't introduce any difference in the initial behavior
            if(!File.Exists(path.ToString()))
                path.Append(pwdTagChar);

            try
            {
                FileStream fStream = File.Open(path.ToString(), FileMode.Open);

                Rijndael RijndaelAlg = Rijndael.Create();
                CryptoStream cStream = new CryptoStream(fStream,
                    RijndaelAlg.CreateDecryptor(encodingKey, encodingIV),
                    CryptoStreamMode.Read);

                object data = DeserializeFromStream(type, cStream);

                cStream.Close();
                fStream.Close();
                return data;
            }
            catch (CryptographicException e)
            {
            }
            catch (UnauthorizedAccessException e)
            {
            }
            catch (FileNotFoundException)
            {
            }
            catch (IOException e)
            {
            }
            return null;
        }

        /// <summary>
        /// Retrieves informations by the cache, if available, accessing them by id.
        /// This method is useful to retrieve any data whose writing access is owned
        /// in an exclusive way.
        /// </summary>
        /// <param name="requiredID">The id of the required data</param>
        /// <returns>
        /// The required data, if available in cache;
        /// otherwise a null reference.
        /// </returns>
        public object GetData(int requiredID)
        {
            if (!m_infoTracer.ContainsKey(requiredID)) 
                return null;
            try
            {
                object data = DeserializeData(requiredID.ToString(), m_infoTracer[requiredID].TypeAtt);
                return data;
            }
            catch (FileNotFoundException)
            {
                m_infoTracer.Remove(requiredID);
                return null;
            }
        }

        /// <summary>
        /// Retrieves informations by the cache, if available, accessing them by id.
        /// If the version param differs from the eventually available one, the latter
        /// is considered as unavailable.
        /// This method is useful to retrieve any data whose writing access isn't 
        /// either owned or owned in an exclusive way.
        /// </summary>
        /// <param name="requiredID">The id of the required data</param>
        /// <param name="version">The version required (available on the remote system)</param>
        /// <returns>
        /// The required data, if available in cache with the indicated version;
        /// otherwise a null reference.
        /// </returns>
        public object GetData(int requiredID, int version)
        {
            if (m_infoTracer.ContainsKey(requiredID))
            {
                CachingInfo infos = m_infoTracer[requiredID];
                if (infos.VersionAtt != version) return null;
                try
                {
                    object data = DeserializeData(requiredID.ToString(), infos.TypeAtt);
                    return data;
                }
                catch (FileNotFoundException)
                {
                    m_infoTracer.Remove(requiredID);
                    return null;
                }
            }
            return null;
        }

        /// <summary>
        /// Updates the data associated to the updatingID param with the new value.
        /// If the id doesn't refers to any information in cache, a new reference is added.
        /// </summary>
        /// <param name="updatingID">The id of the updating data</param>
        /// <param name="updatedValue">The updated value</param>
        /// <returns>The new version</returns>
        public int UpdateData(int updatingID, object updatedValue)
        {
            if (!m_infoTracer.ContainsKey(updatingID))
                m_infoTracer.Add(updatingID, new CachingInfo(updatedValue.GetType()));

            SerializeData(updatingID.ToString(), updatedValue);

            CheckCacheSize();
            return m_infoTracer[updatingID].VersionAtt++;
        }

        /// <summary>
        /// Updates the data associated to the updatingID param with the new value.
        /// If the id doesn't refers to any information in cache, a new reference is added.
        /// </summary>
        /// <param name="updatingID">The id of the updating data</param>
        /// <param name="updatingVers">The version of the updated data</param>
        /// <param name="updatedValue">The updated value</param>
        /// <returns>The new version</returns>
        public int UpdateData(int updatingID, int updatedVers, object updatedValue)
        {
            CachingInfo infos;
            if (!m_infoTracer.ContainsKey(updatingID))
            {
                infos = new CachingInfo(updatedVers, updatedValue.GetType());
                m_infoTracer.Add(updatingID, infos);
            }
            else
            {
                infos = m_infoTracer[updatingID];
                infos.VersionAtt = updatedVers;
            }
            
            SerializeData(updatingID.ToString(), updatedValue);

            CheckCacheSize();
            return m_infoTracer[updatingID].VersionAtt;
        }

        /// <summary>
        /// Gets the username of the last profile used to access the application and stored.
        /// Does not provide any information about password containing.
        /// </summary>
        /// <returns>The last used profile</returns>
        [Obsolete("Does not provide infos about password containing")]
        public string GetLastProfile()
        {
            StringBuilder path = new StringBuilder(m_cachePath);
            DirectoryInfo pDir = new DirectoryInfo(path.Append(profileDir).ToString());

            FileInfo[] files = pDir.GetFiles();
            FileInfo lastFile;
            try
            {
                lastFile = files[0];
            }
            catch (IndexOutOfRangeException)
            {
                //No profile stored in memory
                return null;
            }

            DateTime lastTime = lastFile.LastAccessTime;
            for(int i=0, n=files.Length; i<n; i++)
                if(files[i].LastAccessTime>lastTime)
                {
                    lastFile = files[i];
                    lastTime = lastFile.LastAccessTime;
                }

            return lastFile.Name;
        }

        /// <summary>
        /// Gets the username of all the profiles used to access the application and stored.
        /// The username will be ordered in a descending order with restect to the last access time.
        /// </summary>
        /// <returns>All the stored profiles</returns>
        [Obsolete("Does not provide infos about password containing")]
        public string[] GetProfiles()
        {
            StringBuilder path = new StringBuilder(m_cachePath);
            DirectoryInfo pDir = new DirectoryInfo(path.Append(profileDir).ToString());

            FileInfo[] files = pDir.GetFiles();
            string[] profiles = new string[files.Length];
            bool[] pwded = new bool[files.Length];
            int i = files.Length-1;
            foreach (FileInfo f in files.OrderBy(file => file.LastAccessTime))
            {
                int lastChar = f.Name.Length - 1;
                if (f.Name[lastChar] == pwdTagChar)
                {
                    pwded[i] = false;
                    profiles[i] = f.Name.Substring(0, lastChar);
                }
                else
                {
                    pwded[i] = false;
                    profiles[i] = f.Name;
                }

                i--;
            }

            return profiles;
        }

        /// <summary>
        /// Gets the username of all the profiles used to access the application and stored.
        /// The username will be ordered in a descending order with respect to the last access time.
        /// Each username is followed by a flag indicating whether or not the profile provides also
        /// the password.
        /// </summary>
        /// <param name="profiles">All the stored profiles (May be zero lengthed)</param>
        /// <param name="pwdSaved">The status of profiles with respect to the password providing (May be a zero lengthed)</param>
        public void GetProfiles(out string[] profiles, out bool[] pwdSaved)
        {
            StringBuilder path = new StringBuilder(m_cachePath);
            DirectoryInfo pDir = new DirectoryInfo(path.Append(profileDir).ToString());

            FileInfo[] files = pDir.GetFiles();
            profiles = new string[files.Length]; //string[] profiles = new string[files.Length];
            pwdSaved = new bool[files.Length]; //bool[] pwdSaved = new bool[files.Length];
            int i = files.Length - 1;
            foreach (FileInfo f in files.OrderBy(file => file.LastAccessTime))
            {
                int lastChar = f.Name.Length - 1;
                if (f.Name[lastChar] == pwdTagChar)
                {
                    pwdSaved[i] = false;
                    profiles[i] = f.Name.Substring(0, lastChar);
                }
                else
                {
                    pwdSaved[i] = true;
                    profiles[i] = f.Name;
                }

                i--;
            }
        }
        #region IDisposable Members
        /// <summary>
        /// Stores in local memory the internal structures used to manage caching.
        /// Any termination of an application that does't call Dispose will cause the cacheManager
        /// to be inconsistent with respect to the cache directory, so that any following launching
        /// will clear the cache directory.
        /// </summary>
        public void Dispose()
        {
            StoreTracingTable();
            //SerializeData(tracingTableFile, m_infoTracer);
            /*using (System.IO.FileStream file = new System.IO.FileStream(m_cachePath + tracingTableFile, System.IO.FileMode.OpenOrCreate))
            {
                System.Xml.XmlWriter writer = System.Xml.XmlWriter.Create(file);
                m_infoTracer.WriteXml(writer);
                writer.Close();
            }*/
        }
        #endregion

        #endregion

        #region Private Members
        /// <summary>
        /// Manages the case in which the tracingTable is missing (not created yet, or lost)
        /// and makes the cache directory consistent with the new table state.
        /// </summary>
        /// <returns>The read caching table.</returns>
        private Dictionary<int, CachingInfo> ManageMissingTable()
        {
            Dictionary<int, CachingInfo> newTable = new Dictionary<int, CachingInfo>();
            DirectoryInfo cacheDir = new DirectoryInfo(m_cachePath);
            FileInfo[] files = cacheDir.GetFiles();

            /*
             * If the tracingTable was lost, a new table will be created, and the cache directory 
             * will be made consistent with the new table's state.
             */
            if (files.Length > 0)
                foreach (FileInfo file in files)
                    file.Delete();
            return newTable;
        }

        /// <summary>
        /// Sets the path relative to the application directory for the cache directory.
        /// null value is allowed. If so, the cache path will be set to the default value "cache".
        /// In any case, if not found, the directory will be created.
        /// </summary>
        /// <value>The cache path.</value>
        private string CachePath
        {
            set
            {
                StringBuilder buffer = new StringBuilder(256);
                buffer.Append(GetApplicationDirectory());
                buffer.Append((value == null) ? defaultCacheDir : value);
                buffer.Append(Path.DirectorySeparatorChar);

                m_cachePath = buffer.ToString();
                if (!Directory.Exists(m_cachePath))
                    Directory.CreateDirectory(m_cachePath);

                buffer.Append(Path.DirectorySeparatorChar).Append(profileDir);
                if(!Directory.Exists(buffer.ToString()))
                    Directory.CreateDirectory(buffer.ToString());
            }
        }

        /// <summary>
        /// Performs an autocleaning of the cache directory, deleting all the files older
        /// than the specified TimeSpan.
        /// </summary>
        /// <param name="olderThan">Temporal threshold for removal</param>
        private void AutoClean(TimeSpan olderThan)
        {
            DateTime now = DateTime.Now;

            DirectoryInfo cacheDir = new DirectoryInfo(m_cachePath);
            FileInfo[] files = cacheDir.GetFiles();
            foreach(FileInfo f in files)
            {
                if (now - f.LastAccessTime >= olderThan)
                {
                    m_infoTracer.Remove(Int32.Parse(f.Name));
                    f.Delete();
                }
            }
        }

        /// <summary>
        /// Checks the size of the cache directory, and remove, if necessary the farthest accessed files.
        /// </summary>
        private void CheckCacheSize()
        {
            DirectoryInfo cacheDir = new DirectoryInfo(m_cachePath);

            FileInfo[] files = cacheDir.GetFiles();
            long size = 0;
            foreach (FileInfo file in files)
            {
                size += file.Length;
            }

            if (size < m_maxCacheSize) return;

            //FIX-ME:
            //ALTERNATIVA alla rimozione puntuale potrebbe essere la  liberazione in un'unica volta 
            //di una porzione predefinita di spazio (magari definita da una opportuna proprietà

            ////var orderedFiles = from f in files orderby f.LastAccessTime select f;
            ////var sortedFiles = words.OrderBy(file => file.LastAccessTime, new LastAccessTimeComparer());
            foreach(FileInfo f in files.OrderBy(file => file.LastAccessTime))
            {
                m_infoTracer.Remove(Int32.Parse(f.Name));
                size -= f.Length;
                f.Delete();
                if (size < m_maxCacheSize) break; //execute only a puntual removal strategy
            }
        }

        /// <summary>
        /// Used by asynchronus saving of generic data.
        /// </summary>
        /// <param name="req">The request data.</param>
        private void SaveCriticalData(SaveRequest<object> req)
        {
            SaveCriticalData(req.data, req.fileName);
        }

        /// <summary>
        /// Used by asynchronus saving of credentials.
        /// </summary>
        /// <param name="req">The request credential.</param>
        private void SaveCriticalData(SaveRequest<InternalEntities.Credentials> req)
        {
            SaveCriticalData(req.data, req.fileName);
        }

        /// <summary>
        /// Serializes the data.
        /// </summary>
        /// <param name="id">The id.</param>
        /// <param name="data">The data.</param>
        private void SerializeData(string id, object data)
        {
            string fileName = new StringBuilder(256).Append(m_cachePath).Append(id).ToString();
            using (System.IO.FileStream file = new System.IO.FileStream(fileName, System.IO.FileMode.Create))
                SerializeToStream(data, file);
        }

        /// <summary>
        /// Deserializes the data.
        /// </summary>
        /// <param name="id">The file to deserialize from.</param>
        /// <param name="type">The type expected by the serialization.</param>
        /// <returns>An instance of the deserialized object</returns>
        private object DeserializeData(string id, Type type)
        {
            string fileName = new StringBuilder(256).Append(m_cachePath).Append(id).ToString();

            object data;
            using (System.IO.FileStream file = new System.IO.FileStream(fileName, System.IO.FileMode.Open))
                data = DeserializeFromStream(type, file);
            
            return data;
        }

        /// <summary>
        /// Factoring method for Serialization: executes serialization into a specified Stream.
        /// </summary>
        /// <param name="data">The object to serialize</param>
        /// <param name="stream">The stream to serialize in</param>
        private void SerializeToStream(object data, Stream stream)
        {
            XmlSerializer serializer = new XmlSerializer(data.GetType());
            serializer.Serialize(stream, data);
        }

        /// <summary>
        /// Factoring method for Deserialization: executes deserialization from a specified Stream.
        /// </summary>
        /// <param name="type">The type expected by the serialization.</param>
        /// <param name="stream">The stream from which deserialize</param>
        /// <returns>
        /// The deserialized object;
        /// null reference if an error occurs.
        /// </returns>
        private static object DeserializeFromStream(Type type, Stream stream)
        {
            object data=null;
            XmlSerializer serializer = new XmlSerializer(type);
            //Here is generated an exception if the pbject to deserialize does not have a default constructor...
            //try
            //{
                data = serializer.Deserialize(stream);
            //}
            //catch (InvalidOperationException e)
            //{
            //    System.Diagnostics.Debug.WriteLine("VANDIN:eccezione:"+e.ToString());
            //}

            return data;
        }

        /// <summary>
        /// Serializes critical data, using the Rijndael encryption algorithm.
        /// </summary>
        /// <param name="data">The data to serialize.</param>
        /// <param name="stream">The stream to serialize in.</param>
        private void SerializeCriticalData(object data, Stream stream)
        {
            try
            {
                Rijndael RijndaelAlg = Rijndael.Create();
                CryptoStream cStream = new CryptoStream(stream,
                    RijndaelAlg.CreateEncryptor(encodingKey, encodingIV),
                    CryptoStreamMode.Write);

                SerializeToStream(data, cStream);

                cStream.Close();
            }
            catch (CryptographicException e)
            {
            }
            catch (UnauthorizedAccessException e)
            {
            }
        }

        /// <summary>
        /// Stores the tracing table into a file.
        /// </summary>
        private void StoreTracingTable()
        {
            using (FileStream stream = new FileStream(m_cachePath + tracingTableFile, FileMode.OpenOrCreate))
            {
                XmlWriter writer = XmlWriter.Create(stream);
                foreach (KeyValuePair<int, TripLOAMobile.CacheManager.CachingInfo> info in m_infoTracer)
                {
                    writer.WriteStartElement("item");

                    writer.WriteStartElement("id");
                    writer.WriteValue(info.Key);
                    writer.WriteEndElement();

                    writer.WriteStartElement("info");

                    writer.WriteStartElement("version");
                    writer.WriteValue(info.Value.VersionAtt);
                    writer.WriteEndElement();

                    writer.WriteStartElement("type");
                    writer.WriteString(info.Value.TypeAtt.ToString());
                    writer.WriteEndElement();

                    writer.WriteEndElement();
                }
                writer.Close();
            }
        }

        /// <summary>
        /// Acquires the tracing table from file.
        /// </summary>
        private void AcquireTracingTable()
        {
            using (FileStream stream = new FileStream(m_cachePath + tracingTableFile, FileMode.Open))
            {
                XmlReader reader = XmlReader.Create(stream);
                //XmlNodeReader reader = XmlNodeReader.Create(stream);
                //reader.ReadStartElement();
                while (reader.NodeType != System.Xml.XmlNodeType.EndElement)
                {
                    reader.ReadStartElement("item");

                    reader.ReadStartElement("id");
                    int id = reader.ReadContentAsInt();
                    reader.ReadEndElement();

                    reader.ReadStartElement("info");

                    CachingInfo info = new CachingInfo();
                    reader.ReadStartElement("version");
                    info.VersionAtt = reader.ReadContentAsInt();
                    reader.ReadEndElement();

                    reader.ReadStartElement("type");
                    info.TypeAtt = Type.GetType(reader.ReadContentAsString());
                    reader.ReadEndElement();

                    reader.ReadEndElement();

                    m_infoTracer.Add(id, info);
                }
                reader.ReadEndElement();
            }
        }

        /// <summary>
        /// Gets the application directory.
        /// </summary>
        /// <returns>The fullpath of the application directory, terminated by a DirectorySeparatorChar</returns>
        private static string GetApplicationDirectory()
        {
            StringBuilder str = new StringBuilder(256);
            str.Append(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().GetModules()[0].FullyQualifiedName));
            return str.Append(Path.DirectorySeparatorChar).ToString();
        }

        /// <summary>
        /// Setups the encoding info used to serialize critical data.
        /// Encoding infos include the encoding key and the IV.
        /// </summary>
        private void SetupEncodingInfo()
        {
            //Calculates and initializes key
            byte[] appData = Encoding.ASCII.GetBytes("TripLOAMobile-EncodingKey");

            int appDataSize = appData.Length;
            uint sizeOut = 16;
            byte[] deviceOutput = new byte[16];

            // Calls the native GetDeviceUniqueID method from coredll.dll
            GetDeviceUniqueID(appData, appDataSize, 1, deviceOutput, ref sizeOut);
            if (sizeOut > 0)
                encodingKey = deviceOutput;
            else throw new Exception("Unable to calculate encoding key.");

            //Calculates and initializes IV
            appData = Encoding.ASCII.GetBytes("TripLOAMobile-InitialVector");

            appDataSize = appData.Length;
            sizeOut = 16;
            deviceOutput = new byte[16];

            // Calls the native GetDeviceUniqueID method from coredll.dll
            GetDeviceUniqueID(appData, appDataSize, 1, deviceOutput, ref sizeOut);
            if (sizeOut > 0)
                encodingIV = deviceOutput;
            else throw new Exception("Unable to calculate encoding IV.");
        }

        /// <summary>
        /// This function returns an application-specific hash of the device identifier. 
        /// The application can use this hash to uniquely identify the device.
        /// (Managed wrapping of the same function declared in WM5 SDK).
        /// <seealso cref="http://msdn2.microsoft.com/en-us/library/ms893522.aspx"/>
        /// </summary>
        /// <param name="appdata">
        /// Application-provided data, which is hashed with the device identifier.
        /// This application data must have a minimum length of 8 bytes and should be unique to the application.
        /// </param>
        /// <param name="cbApplictionData">
        /// Length of the application data, pbApplicationData.
        /// This parameter must be at least 8 bytes or the function call will fail.
        /// </param>
        /// <param name="dwDeviceIDVersion">
        /// Version number of the algorithm used to calculate the device identifier returned from GetDeviceUniqueID.
        /// Currently, the only defined version number is 1. This parameter must use this value or the function call will fail. 
        /// </param>
        /// <param name="deviceIDOuput">
        /// Application-provided output buffer for the device identifier.
        /// This buffer should be at least equal to GETDEVICEUNIQUEID_VI_OUTPUT (20 bytes). 
        /// If the provided buffer is smaller than the complete hash, the hash will be truncated
        /// and only the bytes that fit will be copied into the output buffer.
        /// If the buffer is not large enough, GetDeviceUniqueID truncates the output data to fit
        /// the buffer, and then returns HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER). 
        /// </param>
        /// <param name="pcbDeviceIDOutput">
        /// Specifies the length of the device identifier.
        /// The input parameter is the length of the application-supplied buffer. 
        /// The output parameter is the number of bytes written into the output buffer.
        /// If this pointer is equal to NULL, the call to GetDeviceUniqueID will fail.
        /// </param>
        /// <returns>A termination code</returns>
        [DllImport("coredll.dll")]
        private static extern int GetDeviceUniqueID(
            [In, Out] byte[] appdata, 
            int cbApplictionData,
            int dwDeviceIDVersion,
            [In, Out] byte[] deviceIDOuput, 
            ref uint pcbDeviceIDOutput);

        #endregion

        #region AuxiliaryClasses
        /// <summary>
        /// Represents a request for saving, used to implements an asynchronous saving.
        /// </summary>
        /// <typeparam name="T">The type of the data to be saved</typeparam>
        private class SaveRequest<T>
        {
            public T data;
            public string fileName;

            /// <summary>
            /// Initializes a new instance of the <see cref="SaveRequest&lt;T&gt;"/> class.
            /// </summary>
            /// <param name="data">The data.</param>
            /// <param name="fileName">Name of the file.</param>
            public SaveRequest(T data, string fileName)
            {
                this.data = data;
                this.fileName = fileName;
            }

            /*public void Save(object o)
            {
                SaveCriticalData(m_data, m_filename);
            }*/
        }

        /// <summary>
        /// Represents into the tracing table any single item stored in cache. 
        /// </summary>
        private class CachingInfo
        {
            const int baseVersion = 0;

            private int m_version;
            private Type m_type;

            /// <summary>
            /// Initializes a new instance of the <see cref="CachingInfo"/> class.
            /// </summary>
            public CachingInfo() { }

            /// <summary>
            /// Initializes a new instance of the <see cref="CachingInfo"/> class.
            /// </summary>
            /// <param name="version">The version.</param>
            /// <param name="type">The type.</param>
            public CachingInfo(int version, Type type)
            {
                m_version = version;
                m_type = type;
            }

            /// <summary>
            /// Initializes a new instance of the <see cref="CachingInfo"/> class.
            /// </summary>
            /// <param name="type">The type.</param>
            public CachingInfo(Type type)
            {
                m_version = baseVersion;
                m_type = type;
            }

            /// <summary>
            /// Gets or sets the type of the related cached object.
            /// </summary>
            /// <value>The type.</value>
            public Type TypeAtt { get { return m_type; } set { m_type = value; } }
            /// <summary>
            /// Gets or sets the version of the related cached object.
            /// </summary>
            /// <value>The version.</value>
            public int VersionAtt { get { return m_version; } set { m_version = value; } }

            /*public string ToXmlString()
            {
                StringBuilder xml = new StringBuilder();
                XmlWriter writer = XmlWriter.Create(xml);
                writer.WriteStartElement("cachingInfo");
                
                writer.WriteStartElement("version");
                writer.WriteValue(m_version);
                writer.WriteEndElement();

                writer.WriteStartElement("type");
                writer.WriteString(m_type.ToString());
                writer.WriteEndElement();

                writer.WriteEndElement();

                return xml.ToString();
            }*/

        }
        #endregion
    }
}
